package com.xingu.xg.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xingu.xg.cache.Cache;
import com.xingu.xg.common.consts.ComCodeMsg;
import com.xingu.xg.common.consts.ComRT;
import com.xingu.xg.common.consts.ComResult;
import com.xingu.xg.domain.XgMenu;
import com.xingu.xg.mapper.XgMenuMapper;
import com.xingu.xg.service.XgMenuService;
import com.xingu.xg.vo.AddNodeQuery;
import com.xingu.xg.vo.CopyNodeQuery;
import com.xingu.xg.vo.DeleteNodeQuery;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

/**
 * (XgMenu)表服务实现类
 *
 * @author L
 * @since 2024-05-16 00:04:19
 */
@Slf4j
@Service("xgMenuService")
public class XgMenuServiceImpl implements XgMenuService {
    @Resource
    private XgMenuMapper xgMenuMapper;
    @Autowired
    private TransactionTemplate transactionTemplate;
    @Autowired
    private ScheduledThreadPoolExecutor scheduledThreadPoolExecutor;

    @Override
    public ComResult<XgMenu> queryAllByProjectId(String projectId) {
        // 因为每次进入菜单页都要展示对应的下级菜单,所以此处用缓存提高效率,如果是真实场景可以引入guava或者redis
        // 此处缓存存的是下级,这么做的好处是方便修改部分父节点的子节点时,可以只需要删除部分缓存
        // 如果用redis可以用hash或者zset或者list结构都可以实现同等功能

        // 查出对应的菜单
        XgMenu xgMenu = this.xgMenuMapper.queryByProjectId(projectId);
        if (xgMenu == null) {
            return ComRT.fail(ComCodeMsg.BUSINESS_ERROR, "没有对应的菜单");
        }
        // 从缓存获取下级菜单,如果缓存没有会从数据库取
        // 因为做了分级处理,所以能从缓存拿就尽量从缓存拿
        // 比如说第一次从数据库加载过数据  1 下级 2 3 4    2下级5 6   3下级7 8
        // 1因为修改子菜单,缓存被删除了,所以需要从数据库取数据
        // 而2和3的下级还在缓存中,那2和3的下级就从数据库取出来
        setChilrenByCache(xgMenu);

        // 从数据库获取下级菜单并加入到缓存中
        // setChilrenByDB(xgMenu);

        return ComRT.success(xgMenu);
    }


    /**
     * 创建不同级菜单
     *
     * @param addNodeQuery
     * @return
     */

    @Override
    public ComResult addNode(AddNodeQuery addNodeQuery) {
        String selectedId = addNodeQuery.getSelectedId();
        List<XgMenu> xgMenus = addNodeQuery.getXgMenu().stream().sorted(new Comparator<XgMenu>() {
            @Override
            public int compare(XgMenu o1, XgMenu o2) {
                return o1.getLevel() - o2.getLevel();
            }
        }).collect(Collectors.toList());
        XgMenu selected = xgMenuMapper.queryByProjectId(selectedId);
        if (Objects.isNull(selected)) {
            return ComRT.fail(ComCodeMsg.BUSINESS_ERROR, "没有选择的Id");
        }
        String deleteCacheId = null;
        // 传入参数没有父节点 并且新增节点具有前节点或后节点   则为同级节点
        // 增加同级节点肯定有前节点或后节点
        for (XgMenu addNode : xgMenus) {
            if (addNode.getParentId() == null
                    && (addNode.getPreId() != null && addNode.getPreId().equals(selected.getProjectId()))
                    || (addNode.getNextId() != null && addNode.getNextId().equals(selected.getProjectId()))) {
                // 如果添加节点和选中节点同级
                deleteCacheId = addCurrentNode(addNode, selected);
            } else {
                // 如果添加节点和选中节点不同级
                if (addNode.getLevel() > selected.getLevel()) {
                    // 如果添加节点为选中节点的子节点
                    deleteCacheId = addChildNode(addNode, selected);
                } else {
                    // 如果添加节点为选中节点的父节点
                    deleteCacheId = addParentNode(addNode, selected);
                }
            }
            delayDeleteCache(500L, deleteCacheId);
        }
        return ComRT.success();
    }

    /**
     * 创建子节点
     *
     * @param addNode  要添加的节点
     * @param selected 被选中的节点
     * @return 要删除的缓存Key
     */
    private String addCurrentNode(XgMenu addNode, XgMenu selected) {
        String parentId = selected.getParentId();
        if (parentId != null) {
            Cache.MENU_CACHE.remove(parentId);
        }

        transactionTemplate.executeWithoutResult(status -> {
            try {
                addNode.setParentId(parentId);
                addNode.setLevel(selected.getLevel());
                xgMenuMapper.insert(addNode);
                if (addNode.getPreId().equals(selected.getProjectId())) {
                    selected.setNextId(addNode.getProjectId());
                } else {
                    selected.setPreId(addNode.getProjectId());
                }
                xgMenuMapper.updateById(selected);
            } catch (Exception e) {
                log.error(e.getMessage());
                status.setRollbackOnly();
                throw new RuntimeException(e);
            }
        });
        return parentId;
    }

    /**
     * 创建父节点
     *
     * @param addNode  要添加的节点
     * @param selected 被选中的节点
     * @return 要删除的缓存Key
     * <p>
     * 修改选中节点的所有子节点  level+1
     */
    private String addParentNode(XgMenu addNode, XgMenu selected) {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                try {
                    // 将新增节点设置为选中节点的父节点
                    String parentId = selected.getParentId();
                    Cache.MENU_CACHE.remove(parentId);
                    addNode.setParentId(parentId);
                    addNode.setLevel(selected.getLevel());
                    xgMenuMapper.insert(addNode);
                    selected.setParentId(addNode.getProjectId());
                    // 处理选中节点原顺序
                    if (selected.getPreId() != null) {
                        XgMenu preNode = xgMenuMapper.selectOne(new LambdaQueryWrapper<XgMenu>().eq(XgMenu::getProjectId, selected.getPreId()));
                        preNode.setNextId(addNode.getProjectId());
                        addNode.setPreId(preNode.getProjectId());
                        xgMenuMapper.updateById(preNode);
                        selected.setPreId(null);
                    }
                    if (selected.getNextId() != null) {
                        XgMenu nextNode = xgMenuMapper.selectOne(new LambdaQueryWrapper<XgMenu>().eq(XgMenu::getProjectId, selected.getNextId()));
                        nextNode.setPreId(addNode.getProjectId());
                        addNode.setNextId(nextNode.getProjectId());
                        xgMenuMapper.updateById(nextNode);
                        selected.setNextId(null);
                    }
                    xgMenuMapper.updateById(addNode);
                    // 更新选中节点及其子节点的level
                    List<XgMenu> updateList = new ArrayList<>();
                    setChilrenByCache(selected);
                    ArrayList<XgMenu> list = new ArrayList<>();
                    list.add(selected);
                    levelUpdate(list, "+", 1, updateList);
                    updateList.forEach(x -> {
                        int i = xgMenuMapper.updateById(x);
                        if (i != 1) {
                            throw new RuntimeException("更新失败");
                        }
                    });
                    delayDeleteCache(500L, parentId);
                } catch (Exception e) {
                    log.error(e.getMessage());
                    status.setRollbackOnly();
                    throw e;
                }
            }
        });
        return addNode.getProjectId();
    }

    /**
     * 创建子节点
     *
     * @param addNode  要添加的节点
     * @param selected 被选中的节点
     * @return 要删除的缓存Key
     */
    private String addChildNode(XgMenu addNode, XgMenu selected) {
        // 创建节点为选中节点的子节点
        // 选中节点的子节点层级修改
        Cache.MENU_CACHE.remove(addNode.getParentId());
        xgMenuMapper.insert(addNode);
        return addNode.getParentId();
    }

    /**
     * 延迟删除缓存
     *
     * @param delayTime     延迟时间
     * @param deleteCacheId 要删除的缓存Key
     */
    private void delayDeleteCache(Long delayTime, String deleteCacheId) {
        scheduledThreadPoolExecutor.schedule(new Runnable() {
            @Override
            public void run() {
                if (deleteCacheId != null) {
                    Cache.MENU_CACHE.remove(deleteCacheId);
                }
            }
        }, delayTime, TimeUnit.MILLISECONDS);
    }

    @Override
    public void loadMenu() {
        List<XgMenu> xgMenuList = xgMenuMapper.selectList(new QueryWrapper<XgMenu>());
        xgMenuList.stream().filter(x -> x.getParentId() == null).map(x -> {
            List<XgMenu> chilrens = getChilrens(x, xgMenuList);
            Cache.MENU_CACHE.put(x.getProjectId(), chilrens);
            x.setChildren(chilrens);
            return x;
        }).collect(Collectors.toList());
    }

    private List<XgMenu> getChilrens(XgMenu xgMenu, List<XgMenu> all) {
        if (Objects.isNull(xgMenu) || all == null || all.isEmpty()) {
            return Collections.emptyList();
        }
        List<XgMenu> list = all.stream().filter(a -> {
            if (Objects.isNull(a.getParentId())) {
                return false;
            }
            return a.getParentId().equals(xgMenu.getProjectId());
        }).collect(Collectors.toList());
        List<XgMenu> sort = sort(list);
        Cache.MENU_CACHE.put(xgMenu.getProjectId(), sort);
        List<XgMenu> menuList = sort.stream().map(a -> {
            List<XgMenu> chilrens = getChilrens(a, all);
            a.setChildren(chilrens);
            return a;
        }).collect(Collectors.toList());
        return menuList;
    }

    @Transactional
    @Override
    public ComResult copyNode(CopyNodeQuery copyNodeQuery) {
        List<String> copyList = copyNodeQuery.getList();
        String selectedId = copyNodeQuery.getSelectedId();
        LambdaQueryWrapper<XgMenu> wrapper = new LambdaQueryWrapper<XgMenu>().eq(XgMenu::getProjectId, selectedId);
        XgMenu selected = xgMenuMapper.selectOne(wrapper);
        Cache.MENU_CACHE.remove(selectedId);
        // 从上向下逐层寻找节点
        // 查询所有选中节点
        List<XgMenu> xgMenuList = xgMenuMapper.selectBatchIds(copyList);
        // 查询最上层节点
        Integer minLevel = Integer.MAX_VALUE;
        for (XgMenu xgMenu : xgMenuList) {
            if (xgMenu.getLevel() < minLevel) {
                minLevel = xgMenu.getLevel();
            }
        }
        ArrayList<XgMenu> upList = new ArrayList<>();
        for (XgMenu xgMenu : xgMenuList) {
            if (xgMenu.getLevel().equals(minLevel)) {
                upList.add(xgMenu);
            }
        }
        // 给每一层节点设置子节点
        List<XgMenu> collect = upList.stream().map(x -> {
            List<XgMenu> chilrens = getChilrens(x, xgMenuList);
            x.setChildren(chilrens);
            return x;
        }).collect(Collectors.toList());
        // 设置新增子节点和旧子节点的顺序关系
        if (collect != null && !collect.isEmpty()) {
            collect.get(0).setPreId(null);
        }
        XgMenu originallyFirstNode = null;
        for (int i = 0; i < collect.size(); i++) {
            if (i + 1 < collect.size()) {
                XgMenu preNode = collect.get(i);
                XgMenu nextNode = collect.get(i + 1);
                preNode.setPreId(null);
                preNode.setNextId(nextNode.getProjectId());
                nextNode.setPreId(preNode.getProjectId());
            } else {
                XgMenu preNode = collect.get(i);
                List<XgMenu> chilrens = getChilrensByCache(selectedId);
                if (chilrens == null || chilrens.isEmpty()) {
                    preNode.setNextId(null);
                    continue;
                }
                originallyFirstNode = chilrens.get(0);
                preNode.setNextId(originallyFirstNode.getProjectId());
            }
        }
        // 插入操作,子节点需要父节点的projectId
        for (XgMenu xgMenu : collect) {
            xgMenu.setParentId(selectedId);
            depthSetParentIdAndLevelInsert(xgMenu, selected.getLevel() + 1);
        }
        if (originallyFirstNode != null) {
            originallyFirstNode.setPreId(collect.get(collect.size() - 1).getProjectId());
            xgMenuMapper.updateById(originallyFirstNode);
        }
        delayDeleteCache(500L, selectedId);
        return ComRT.success();
    }

    private List<XgMenu> getChilrensByCache(XgMenu xgMenu) {
        if (xgMenu == null) {
            log.error("未知的菜单ID:"+"projectId");
            throw new RuntimeException("没有对应的菜单节点");
        }
        setChilrenByCache(xgMenu);
        return Cache.MENU_CACHE.get(xgMenu.getProjectId());
    }

    private List<XgMenu> getChilrensByCache(String projectId) {
        XgMenu xgMenu = xgMenuMapper.selectOne(new LambdaQueryWrapper<XgMenu>().eq(XgMenu::getProjectId, projectId));
        if (xgMenu == null) {
            log.error("未知的菜单ID:"+"projectId");
            throw new RuntimeException("没有对应的菜单节点");
        }
        setChilrenByDB(xgMenu);
        return Cache.MENU_CACHE.get(xgMenu.getProjectId());
    }


    @Override
    public ComResult deleteNode(DeleteNodeQuery deleteNodeQuery) {
        List<String> deleteList = deleteNodeQuery.getDeleteList();
        ArrayList<XgMenu> updateList = new ArrayList<>();
        for (String deleteNode : deleteList) {
            XgMenu selected = xgMenuMapper.selectOne(new LambdaQueryWrapper<XgMenu>().eq(XgMenu::getProjectId, deleteNode));
            if (selected == null) {
                continue;
            }
            // 获取被删除节点的所有子节点
            List<XgMenu> currentChilrens = getChilrensByCache(selected);
            if (currentChilrens != null && !currentChilrens.isEmpty()) {
                // 设置被删除节点的子节点为被删除节点的同级节点
                XgMenu chilrenHeadNode = currentChilrens.get(0);
                XgMenu chilrenLastNode = currentChilrens.get(currentChilrens.size() - 1);
                if (selected.getPreId() != null) {
                    XgMenu preNode = xgMenuMapper.selectOne(new LambdaQueryWrapper<XgMenu>().eq(XgMenu::getProjectId, selected.getPreId()));
                    setPreAndNext(preNode, chilrenHeadNode, null);
                    updateList.add(preNode);
                }
                if (selected.getNextId() != null) {
                    XgMenu nextNode = xgMenuMapper.selectOne(new LambdaQueryWrapper<XgMenu>().eq(XgMenu::getProjectId, selected.getNextId()));
                    setPreAndNext(null, chilrenLastNode, nextNode);
                    updateList.add(nextNode);
                }

                // 修改被删除节点的子节点的等级  除一级子节点外  只有level进行了修改
                ArrayList<XgMenu> updateChilrens = new ArrayList<>();
                levelUpdate(currentChilrens, "-", 1, updateChilrens);
                updateList.addAll(updateChilrens);
            }

            // 清除缓存  删除数据
            Cache.MENU_CACHE.remove(selected.getParentId());
            Cache.MENU_CACHE.remove(selected.getProjectId());
            Boolean execute = transactionTemplate.execute(new TransactionCallback<Boolean>() {
                @Override
                public Boolean doInTransaction(TransactionStatus status) {
                    try {
                        xgMenuMapper.delete(new LambdaQueryWrapper<XgMenu>().eq(XgMenu::getProjectId, selected.getProjectId()));
                        // 此处其实先删除再插入会更好   但是我不确定数据量,我看图片有展开至..级,所以最好有level字段方便查询
                        // 如果不要level字段也可以,在查询递归的时候增加个变量就好了,但是新增节点的时候就需要改变传入的参数了
                        // 假如说我要删除1级节点,如果1级下有几千个子节点的话,全部做逻辑删除,再插入新节点,性能会更差
                        // 而且数据库会有很多多余数据
                        // 我问增删改频繁吗    回我说增删改查挺频繁的
                        for (XgMenu xgMenu : updateList) {
                            int i = xgMenuMapper.updateById(xgMenu);
                            if (i!=1){
                                status.setRollbackOnly();
                                return false;
                            }
                        }
                        return true;
                    } catch (Exception e) {
                        log.error(e.getMessage());
                        // 如果发生异常，则标记事务为回滚
                        status.setRollbackOnly();
                        return false;
                    }
                }
            });
            if (!execute) {
                return ComRT.fail(ComCodeMsg.BUSINESS_ERROR, "有其他修改,或发生未知异常");
            }
            delayDeleteCache(500L, selected.getParentId());
            delayDeleteCache(500L, selected.getProjectId());
        }
        return ComRT.success();
    }

    @Override
    public ComResult add(XgMenu xgMenu) {
        xgMenuMapper.insert(xgMenu);
        return ComRT.success();
    }

    private void levelUpdate(List<XgMenu> list, String operation, Integer num, List<XgMenu> updateList) {
        if (list == null) {
            return;
        }
        for (XgMenu xgMenu : list) {
            switch (operation) {
                case "+":
                    xgMenu.setLevel(xgMenu.getLevel() + num);
                    break;
                case "-":
                    xgMenu.setLevel(xgMenu.getLevel() - num);
                    break;
                default:
                    log.error("levelUpdate(List<XgMenu> list, String operation, Integer num, List<XgMenu> updateList) 传入参数二:"+operation);
                    throw new RuntimeException("未知操作");
            }
            updateList.add(xgMenu);
            if (!xgMenu.getLeaf()) {
                levelUpdate(xgMenu.getChildren(), operation, num, updateList);
            }
        }
    }

    /**
     * 设置前后节点
     *
     * @param preNode     前节点
     * @param currentNode 当前节点
     * @param nextNode    后节点
     */
    private void setPreAndNext(XgMenu preNode, XgMenu currentNode, XgMenu nextNode) {
        if (preNode != null) {
            preNode.setNextId(currentNode.getProjectId());
            currentNode.setPreId(preNode.getProjectId());
        }
        if (nextNode != null) {
            nextNode.setPreId(currentNode.getParentId());
            currentNode.setNextId(nextNode.getProjectId());
        }
    }

    private void depthSetParentIdAndLevelInsert(XgMenu xgMenu, Integer level) {
        xgMenu.setProjectId(null);
        xgMenuMapper.insert(xgMenu);
        List<XgMenu> childrens = xgMenu.getChildren();
        if (childrens == null || childrens.isEmpty()) {
            return;
        }
        childrens.forEach(x -> {
            x.setLevel(level);
            x.setParentId(xgMenu.getProjectId());
            depthSetParentIdAndLevelInsert(x, level + 1);
        });
    }


    /**
     * 从缓存获取对应菜单
     */
    private void setChilrenByCache(XgMenu xgMenu) {
        List<XgMenu> chilrens = Cache.MENU_CACHE.get(xgMenu.getProjectId());
        if (xgMenu.getLeaf()) {
            return;
        } else if (chilrens == null || chilrens.isEmpty()) {
            setChilrenByDB(xgMenu);
        }
        chilrens = xgMenu.getChildren();
        if (chilrens == null) {
            return;
        }
        for (XgMenu chilren : chilrens) {
            setChilrenByCache(chilren);
        }
    }

    /**
     * 从数据获取下级菜单
     *
     * @param xgMenu
     */
    private void setChilrenByDB(XgMenu xgMenu) {
        ReentrantLock reentrantLock = new ReentrantLock();
        reentrantLock.lock();
        try {
            List<XgMenu> cacheXgmenu = Cache.MENU_CACHE.get(xgMenu.getProjectId());
            if (!Objects.isNull(cacheXgmenu)) {
                xgMenu.setChildren(cacheXgmenu);
                return;
            }
            List<XgMenu> chilrens = xgMenuMapper.getChilrenByProjectId(xgMenu.getProjectId());
            if (Objects.isNull(chilrens) || chilrens.size() <= 0) {
                xgMenu.setLeaf(true);
                return;
            }
            List<XgMenu> sorted = sort(chilrens);
            xgMenu.setChildren(sorted);
            Cache.MENU_CACHE.put(xgMenu.getProjectId(), sorted);
            for (XgMenu chilren : sorted) {
                setChilrenByDB(chilren);
            }
        } finally {
            reentrantLock.unlock();
        }
    }

    /**
     * 给下级菜单排序
     *
     * @param xgMenus
     * @return
     * @throws RuntimeException
     */
    private List<XgMenu> sort(List<XgMenu> xgMenus) throws RuntimeException {
        if (xgMenus == null || xgMenus.isEmpty()) {
            return new ArrayList<>();
        }
        ArrayList<XgMenu> list = new ArrayList<>();
        List<XgMenu> collect = xgMenus.stream().filter(xgMenu -> xgMenu.getPreId() == null).collect(Collectors.toList());
        if (collect.size() > 1) {
            throw new RuntimeException("数据有误");
        }
        Map<String, XgMenu> map = xgMenus.stream().collect(Collectors.toMap(k -> k.getProjectId(), v -> v));
        XgMenu xgMenu = collect.get(0);
        while (xgMenu != null) {
            list.add(xgMenu);
            xgMenu = map.get(xgMenu.getNextId());
        }
        return list;

    }
}